/********************************************************************************
 * Copyright (c) 2011-2017 Red Hat Inc. and/or its affiliates and others
 *
 * This program and the accompanying materials are made available under the 
 * terms of the Apache License, Version 2.0 which is available at
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * SPDX-License-Identifier: Apache-2.0 
 ********************************************************************************/
package org.eclipse.ceylon.compiler.js;

import static org.eclipse.ceylon.compiler.js.util.TypeUtils.encodeForRuntime;
import static org.eclipse.ceylon.compiler.js.util.TypeUtils.getTypes;
import static org.eclipse.ceylon.compiler.js.util.TypeUtils.isStaticWithGenericContainer;
import static org.eclipse.ceylon.compiler.js.util.TypeUtils.matchTypeParametersWithArguments;
import static org.eclipse.ceylon.compiler.js.util.TypeUtils.printTypeArguments;
import static org.eclipse.ceylon.compiler.typechecker.util.NativeUtil.isForBackend;
import static org.eclipse.ceylon.compiler.typechecker.util.NativeUtil.isHeaderWithoutBackend;
import static org.eclipse.ceylon.compiler.typechecker.util.NativeUtil.isNativeHeader;
import static org.eclipse.ceylon.compiler.typechecker.util.NativeUtil.mergeStatements;
import static org.eclipse.ceylon.model.typechecker.model.ModelUtil.getContainingClassOrInterface;
import static org.eclipse.ceylon.model.typechecker.model.ModelUtil.getContainingDeclaration;
import static org.eclipse.ceylon.model.typechecker.model.ModelUtil.getNativeDeclaration;
import static org.eclipse.ceylon.model.typechecker.model.ModelUtil.isTypeUnknown;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;

import org.eclipse.ceylon.common.Backend;
import org.eclipse.ceylon.compiler.js.GenerateJsVisitor.InitDeferrer;
import org.eclipse.ceylon.compiler.js.GenerateJsVisitor.PrototypeInitCallback;
import org.eclipse.ceylon.compiler.js.GenerateJsVisitor.SuperVisitor;
import org.eclipse.ceylon.compiler.js.util.JsIdentifierNames;
import org.eclipse.ceylon.compiler.js.util.TypeComparator;
import org.eclipse.ceylon.compiler.js.util.TypeUtils;
import org.eclipse.ceylon.compiler.typechecker.tree.Node;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree;
import org.eclipse.ceylon.compiler.typechecker.tree.Tree.StaticType;
import org.eclipse.ceylon.compiler.typechecker.util.NativeUtil;
import org.eclipse.ceylon.model.typechecker.model.Class;
import org.eclipse.ceylon.model.typechecker.model.ClassOrInterface;
import org.eclipse.ceylon.model.typechecker.model.Constructor;
import org.eclipse.ceylon.model.typechecker.model.Declaration;
import org.eclipse.ceylon.model.typechecker.model.Function;
import org.eclipse.ceylon.model.typechecker.model.Interface;
import org.eclipse.ceylon.model.typechecker.model.ModelUtil;
import org.eclipse.ceylon.model.typechecker.model.Parameter;
import org.eclipse.ceylon.model.typechecker.model.ParameterList;
import org.eclipse.ceylon.model.typechecker.model.Scope;
import org.eclipse.ceylon.model.typechecker.model.Type;
import org.eclipse.ceylon.model.typechecker.model.TypeDeclaration;
import org.eclipse.ceylon.model.typechecker.model.TypeParameter;
import org.eclipse.ceylon.model.typechecker.model.TypedDeclaration;
import org.eclipse.ceylon.model.typechecker.model.Value;

public class TypeGenerator {

    static final ErrorVisitor errVisitor = new ErrorVisitor();

    /** Generates a function to initialize the specified type. 
     * @param initDeferrer */
    static void initializeType(final Node type, final GenerateJsVisitor gen, InitDeferrer initDeferrer) {
        Tree.ExtendedType extendedType = null;
        Tree.SatisfiedTypes satisfiedTypes = null;
        final ClassOrInterface decl;
        final List<Tree.Statement> stmts;
        Value objDecl = null;
        if (type instanceof Tree.ClassDefinition) {
            Tree.ClassDefinition classDef = (Tree.ClassDefinition) type;
            extendedType = classDef.getExtendedType();
            satisfiedTypes = classDef.getSatisfiedTypes();
            decl = classDef.getDeclarationModel();
            Tree.Declaration nh = gen.getNativeHeader(decl);
            if (nh == null && NativeUtil.hasNativeMembers(decl)) {
                nh = classDef;
            }
            stmts = NativeUtil.mergeStatements(classDef.getClassBody(), nh, Backend.JavaScript);
        } else if (type instanceof Tree.InterfaceDefinition) {
            satisfiedTypes = ((Tree.InterfaceDefinition) type).getSatisfiedTypes();
            decl = ((Tree.InterfaceDefinition) type).getDeclarationModel();
            final Tree.InterfaceDefinition idef = (Tree.InterfaceDefinition)type;
            Tree.Declaration nh = gen.getNativeHeader(decl);
            if (nh == null && NativeUtil.hasNativeMembers(decl)) {
                nh = idef;
            }
            stmts = NativeUtil.mergeStatements(idef.getInterfaceBody(), nh, Backend.JavaScript);
        } else if (type instanceof Tree.ObjectDefinition) {
            Tree.ObjectDefinition objectDef = (Tree.ObjectDefinition) type;
            extendedType = objectDef.getExtendedType();
            satisfiedTypes = objectDef.getSatisfiedTypes();
            decl = (ClassOrInterface)objectDef.getDeclarationModel().getTypeDeclaration();
            objDecl = objectDef.getDeclarationModel();
            Tree.Declaration nh = gen.getNativeHeader(decl);
            if (nh == null && NativeUtil.hasNativeMembers(decl)) {
                nh = objectDef;
            }
            stmts = NativeUtil.mergeStatements(objectDef.getClassBody(), nh,Backend.JavaScript);
        } else if (type instanceof Tree.ObjectExpression) {
            Tree.ObjectExpression objectDef = (Tree.ObjectExpression) type;
            extendedType = objectDef.getExtendedType();
            satisfiedTypes = objectDef.getSatisfiedTypes();
            decl = (ClassOrInterface)objectDef.getAnonymousClass();
            stmts = objectDef.getClassBody().getStatements();
        } else if (type instanceof Tree.Enumerated) {
            Tree.Enumerated vc = (Tree.Enumerated)type;
            stmts = vc.getBlock().getStatements();
            decl = (ClassOrInterface)vc.getDeclarationModel().getTypeDeclaration().getContainer();
        } else {
            stmts = null;
            decl = null;
        }
        final PrototypeInitCallback callback = new PrototypeInitCallback() {
            @Override
            public void addToPrototypeCallback() {
                if (decl != null) {
                    gen.addToPrototype(type, decl, stmts);
                }
            }
        };
        typeInitialization(extendedType, satisfiedTypes, decl, callback, gen, objDecl, initDeferrer);
    }

    /** This is now the main method to generate the type initialization code.
     * @param extendedType The type that is being extended.
     * @param satisfiedTypes The types satisfied by the type being initialized.
     * @param d The declaration for the type being initialized
     * @param callback A callback to add something more to the type initializer in prototype style.
     * @param initDeferrer something which lets us put statements at the end of the container initialiser, if it's not null (it's null for toplevels)
     */
    static void typeInitialization(final Tree.ExtendedType extendedType, final Tree.SatisfiedTypes satisfiedTypes,
            final ClassOrInterface d, PrototypeInitCallback callback, final GenerateJsVisitor gen,
            final Value objectDeclaration, InitDeferrer initDeferrer) {

        final boolean isInterface = d instanceof org.eclipse.ceylon.model.typechecker.model.Interface;
        String initFuncName = isInterface ? "initTypeProtoI" : "initTypeProto";

        final String typename = gen.getNames().name(d);
        final String initname;
        if (d.isAnonymous()) {
            String _initname = gen.getNames().objectName(d);
            if (d.isToplevel()) {
                initname = "$i$" + _initname.substring(0, _initname.length()-2);
            } else {
                initname = "$i$" + _initname;
            }
        } else {
            initname = "$i$" + typename;
        }
        gen.out("function ", initname, "()");
        gen.beginBlock();
        gen.out("if(", typename, ".$$===undefined)");
        gen.beginBlock();
        boolean genIniter = true;
        if (TypeUtils.isNativeExternal(d)) {
            //Allow native types to have their own initialization code
            genIniter = !gen.stitchInitializer(d);
        }
        if (genIniter) {
            gen.out(gen.getClAlias(), initFuncName, "(", typename, ",'", d.getQualifiedNameString(), "'");
            final List<Tree.StaticType> supers = satisfiedTypes == null ? Collections.<Tree.StaticType>emptyList()
                    : new ArrayList<Tree.StaticType>(satisfiedTypes.getTypes().size()+1);

            if (extendedType != null) {
                if (satisfiedTypes == null) {
                    String fname = typeFunctionName(extendedType.getType(), d, gen);
                    gen.out(",", fname);
                } else {
                    supers.add(extendedType.getType());
                }
            } else if (!isInterface) {
                gen.out(",", gen.getClAlias(), "Basic");
            }
            if (satisfiedTypes != null) {
                supers.addAll(satisfiedTypes.getTypes());
                Collections.sort(supers, new StaticTypeComparator());
                for (Tree.StaticType satType : supers) {
                    String fname = typeFunctionName(satType, d, gen);
                    gen.out(",", fname);
                }
            }
            gen.out(");");
        }
        //Add ref to outer type
        if (d.isMember()) {
            StringBuilder containers = new StringBuilder();
            Scope _d2 = d;
            while (_d2 instanceof ClassOrInterface) {
                if (containers.length() > 0) {
                    containers.insert(0, '.');
                }
                containers.insert(0, gen.getNames().name((Declaration)_d2));
                _d2 = _d2.getContainer();
            }
            gen.endLine();
            gen.out(containers.toString(), "=", typename, ";");
        }

        //The class definition needs to be inside the init function if we want forwards decls to work in prototype style
        if (gen.opts.isOptimize()) {
            gen.endLine();
            callback.addToPrototypeCallback();
        }
        gen.endBlockNewLine();
        gen.out("return ", typename, ";");
        gen.endBlockNewLine();
        //If it's nested, share the init function
        if (d.isStatic()) {
            gen.out(gen.getNames().name(ModelUtil.getContainingClassOrInterface(d.getContainer())),
                    ".$st$.", initname, "=", initname, ";");
        } else if (gen.outerSelf(d)) {
            gen.out(".", initname, "=", initname, ";");
        }
        if (initDeferrer != null) {
            initDeferrer.deferred.add(initname+"();");
        } else {
            gen.out(initname, "();");
        }
    }

    /** Returns the name of the type or its $i$ function if it's local. */
    static String typeFunctionName(final Tree.StaticType type, final ClassOrInterface coi,
            final GenerateJsVisitor gen) {
        TypeDeclaration d = type.getTypeModel().getDeclaration();
        final boolean removeAlias = d == null || !d.isClassOrInterfaceMember() || d instanceof Interface;
        if ((removeAlias && d.isAlias()) || d instanceof Constructor) {
            Type extendedType = d.getExtendedType();
            d = extendedType==null ? null : extendedType.getDeclaration();
        }
        Declaration cont = ModelUtil.getContainingDeclaration(d);
        final boolean inProto = gen.opts.isOptimize()
                && cont instanceof TypeDeclaration;
        final boolean imported = gen.isImported(type.getUnit().getPackage(), d);
        String dname = gen.getNames().name(d);
        if (d.isAlias()) {
            TypeDeclaration d2 = d;
            while (d2.isAlias()) {
                d2 = d2.getExtendedType().getDeclaration();
            }
            dname = gen.getNames().name(d2);
        }
        final String initName = "$i$" + dname + "()";
        if (!imported && !d.isClassOrInterfaceMember()) {
            return initName;
        }
        if (inProto && coi.isMember() && !d.isAlias() && (coi.getContainer() == cont
                || ModelUtil.contains(d, coi))) {
            //A member class that extends or satisfies another member of its same container,
            //use its $i$ function
            return initName;
        }
        String tfn;
        //#628 If coi is anonymous and inside cont, qualify the path from cont instead
        if (coi != null && coi.isAnonymous() && cont instanceof Scope && ModelUtil.contains((Scope)cont, coi)) {
            tfn = gen.qualifiedPath(type, cont, inProto);
        } else if (inProto && d.isClassOrInterfaceMember()) {
            return pathToType(type, d, gen);
        } else {
            tfn = gen.qualifiedPath(type, d, inProto);
        }
        tfn = gen.memberAccessBase(type, d, false, tfn);
        if (removeAlias && !imported) {
            int idx = tfn.lastIndexOf('.');
            if (idx > 0) {
                tfn = tfn.substring(0, idx+1) + initName;
            } else {
                tfn = initName;
            }
        }
        return tfn;
    }

    static String pathToType(Node that, TypeDeclaration d, GenerateJsVisitor gen) {
        List<TypeDeclaration> parents = new ArrayList<>(3);
        TypeDeclaration path=d;
        parents.add(path);
        while (path.isClassOrInterfaceMember()) {
            path = ModelUtil.getContainingClassOrInterface(path.getContainer());
            parents.add(0, path);
        }
        StringBuilder sb = new StringBuilder();
        String qp = gen.qualifiedPath(that, parents.get(0), gen.opts.isOptimize()
                && ModelUtil.getContainingDeclaration(d) instanceof TypeDeclaration);
        if (qp != null && !qp.isEmpty()) {
            sb.append(qp);
        }
        boolean first = true;
        for (TypeDeclaration td : parents) {
            if (first) {
                first = false;
            } else if (td.isStatic()) {
                sb.append(".$st$");
            } else {
                sb.append(".$$.prototype");
            }
            if (sb.length() > 0) {
                sb.append('.');
            }
            if (!td.isAlias()) {
                sb.append("$i$");
            }
            sb.append(gen.getNames().name(td));
            if (!td.isAlias()) {
                sb.append("()");
            }
        }
        return sb.toString();
    }

    static void interfaceDefinition(final Tree.InterfaceDefinition that, 
            final GenerateJsVisitor gen, InitDeferrer initDeferrer) {
        //Don't even bother with nodes that have errors
        if (errVisitor.hasErrors(that))return;
        final Interface d = that.getDeclarationModel();
        //If it's inside a dynamic interface, don't generate anything
        Scope container = d.getContainer();
        if (d.isClassOrInterfaceMember() 
                && ((ClassOrInterface)container).isDynamic())
            return;
        
        final Interface natd = (Interface) 
                getNativeDeclaration(d, Backend.JavaScript);
        final boolean headerWithoutBackend = 
                isHeaderWithoutBackend(that, Backend.JavaScript);
        if (natd!=null && 
                (headerWithoutBackend || isNativeHeader(that))) {
            // It's a native header, remember it for later when we deal with its implementation
            gen.saveNativeHeader(that);
            return;
        }
        if (!isForBackend(that, Backend.JavaScript) 
                && !headerWithoutBackend) {
            return;
        }
        gen.comment(that);

        JsIdentifierNames names = gen.getNames();
        gen.out(GenerateJsVisitor.function, names.name(d));
        final boolean withTargs = 
                generateParameters(that.getTypeParameterList(), 
                        null, d, gen);
        gen.beginBlock();
        final List<Declaration> superDecs = new ArrayList<>(3);
        if (!gen.opts.isOptimize()) {
            new SuperVisitor(superDecs).visit(that.getInterfaceBody());
        }
        final Tree.SatisfiedTypes sats = that.getSatisfiedTypes();
        if (withTargs) {
            gen.out(gen.getClAlias(), "set_type_args(", names.self(d),
                    ",$a$,", names.name(d), ")");
            gen.endLine(true);
        }
        callSupertypes(sats == null ? null : getTypes(sats.getTypes()),
                null, d, that, superDecs, null, null, gen);
        if (!d.isToplevel() 
                && container instanceof Function 
                && !((Function)container).getTypeParameters().isEmpty()) {
            gen.out(gen.getClAlias(), "set_type_args(", names.self(d),
                    ",", names.typeArgsParamName((Function)container), ",",
                    names.name(d), ")");
            gen.endLine(true);
        }
        final List<Tree.Statement> stmts;
        if (NativeUtil.isForBackend(d, Backend.JavaScript)) {
            Tree.Declaration nh = gen.getNativeHeader(d);
            if (nh == null && NativeUtil.hasNativeMembers(d)) {
                nh = that;
            }
            stmts = mergeStatements(that.getInterfaceBody(), 
                    nh, Backend.JavaScript);
        } else {
            stmts = that.getInterfaceBody().getStatements();
        }
        gen.visitStatements(stmts);
        //returnSelf(d);
        gen.endBlockNewLine();
        if (d.isDynamic()) {
            //Add the list of expected members here
            final List<Declaration> members = d.getMembers();
            gen.out(names.name(d), ".dynmem$=[");
            if (members.isEmpty()) {
                gen.out("];");
            } else {
                gen.out("'");
                boolean first = true;
                for (Declaration m : members) {
                    if (first) {
                        first=false; 
                    }
                    else {
                        gen.out("','");
                    }
                    gen.out(names.name(m));
                }
                gen.out("'];");
            }
        }
        //Add reference to metamodel
        gen.out(names.name(d), ".$m$=");
        encodeForRuntime(that, d, that.getAnnotationList(), gen);
        gen.endLine(true);
        gen.share(d);
        initializeType(that, gen, initDeferrer);
    }

    /** Outputs the parameter list of the type's constructor, 
     * including surrounding parens.
     * Returns true if the type has type parameters. */
    static boolean generateParameters(final Tree.TypeParameterList tparms,
            final Tree.ParameterList plist, final TypeDeclaration d, 
            final GenerateJsVisitor gen) {
        gen.out("(");
        final boolean withTargs = 
                (tparms!=null && !tparms.getTypeParameterDeclarations().isEmpty()) 
                || isStaticWithGenericContainer(d);
        if (plist != null) {
            for (Tree.Parameter p: plist.getParameters()) {
                p.visit(gen);
                gen.out(",");
            }
        }
        if (withTargs) {
            gen.out("$a$,");
        }
        gen.out(gen.getNames().self(d), ")");
        return withTargs;
    }

    static void callSuperclass(final Tree.SimpleType extendedType, 
            final Tree.InvocationExpression invocation,
            final Class d, 
            final ParameterList plist, 
            final Node that, 
            final boolean pseudoAbstractConstructor,
            final List<Declaration> superDecs, 
            final GenerateJsVisitor gen) {
        TypeDeclaration typeDecl = 
                extendedType.getDeclarationModel();
        if (invocation != null) {
            Tree.Primary primary = invocation.getPrimary();
            Tree.PositionalArgumentList argList = 
                    invocation.getPositionalArgumentList();
            JsIdentifierNames names = gen.getNames();
            
            final String qpath;
            if (typeDecl instanceof Constructor) {
                TypeDeclaration td = 
                        (TypeDeclaration) 
                            typeDecl.getContainer();
                final String path = gen.qualifiedPath(that, 
                        td, false);
                if (path.isEmpty()) {
                    qpath = names.name(td);
                } else {
                    qpath = path + "." + names.name(td);
                }
            } else {
                if (typeDecl.isStatic()) {
                    Declaration cont = getContainingDeclaration(typeDecl);
                    String qp = gen.qualifiedPath(that, cont, false);
                    qpath = qp + (qp.isEmpty() ? "" : ".") + names.name(cont);
                } else {
                    qpath = gen.qualifiedPath(that, typeDecl, false);
                }
            }
            
            if (pseudoAbstractConstructor) {
                if (typeDecl instanceof Constructor) {
                    gen.out(gen.memberAccessBase(extendedType, typeDecl, false, qpath), 
                            "$$a(");
                } else {
                    gen.out(gen.memberAccessBase(extendedType, typeDecl, false, qpath),
                            names.constructorSeparator(typeDecl), 
                            "$c$$$a(");
                }
            } else {
                gen.out(gen.memberAccessBase(extendedType, typeDecl, false, qpath),
                        gen.opts.isOptimize() 
                            && gen.getSuperMemberScope(extendedType) != null ? 
                                    ".call(this," : "(");
            }

            gen.getInvoker()
                .generatePositionalArguments(primary, argList, 
                        false, false);
            List<Tree.PositionalArgument> positionalArgs = 
                    argList.getPositionalArguments();
            if (!positionalArgs.isEmpty()) {
                gen.out(",");
            }
            
            //There may be defaulted args we must pass as undefined
            if (plist!=null) {
                List<Parameter> parameters = plist.getParameters();
                for (int i=positionalArgs.size(); 
                        i<parameters.size(); 
                        i++) {
                    Parameter p = parameters.get(i);
                    if (p.isSequenced()) {
                        gen.out(gen.getClAlias(), "empty(),");
                    } else {
                        gen.out("undefined,");
                    }
                }
            }
            
            //If the supertype has type arguments, add them to the call
            List<TypeParameter> typeParams;
            if (typeDecl instanceof Constructor) {
                //Output the type arguments to the constructor,
                //UNLESS you're in the same class, then just pass
                //the type arguments object
                typeParams = ((Class)typeDecl.getContainer()).getTypeParameters();
                if (typeParams != null && !typeParams.isEmpty()) {
                    typeParams = null;
                    if (ModelUtil.contains(d, typeDecl)) {
                        gen.out("$a$,");
                    } else {
                        printTypeArguments(that, gen, false, 
                                extendedType.getTypeModel()
                                    .getQualifyingType()
                                    .getTypeArguments(),
                                null);
                        gen.out(",");
                    }
                }
            } else {
                typeParams = typeDecl.getTypeParameters();
            }
            
            if (typeParams != null && !typeParams.isEmpty()) {
                List<Type> typeArgs = null;
                if (extendedType.getTypeArgumentList() != null) {
                    typeArgs = extendedType.getTypeArgumentList()
                                    .getTypeModels();
                }
                printTypeArguments(that, gen, false, 
                        matchTypeParametersWithArguments(typeParams, typeArgs),
                        null);
                gen.out(",");
            }
            gen.out(names.self(d), ")");
            gen.endLine(true);
        }
        copySuperMembers(typeDecl, superDecs, d, gen);
    }

    static void callSupertypes(final List<Type> sats, 
            final Tree.SimpleType supertype,
            final ClassOrInterface d, 
            final Node that, 
            final List<Declaration> superDecs,
            final Tree.InvocationExpression invoke, 
            final ParameterList plist, 
            final GenerateJsVisitor gen) {
        if (sats != null) {
            final ArrayList<Type> supers = 
                    new ArrayList<>(sats.size()+1);
            supers.addAll(sats);
            if (supertype != null) {
                supers.add(supertype.getTypeModel());
            }
            Collections.sort(supers, new TypeComparator());
            HashSet<String> myTypeArgs = new HashSet<>();
            for (TypeParameter tp : d.getTypeParameters()) {
                myTypeArgs.add(tp.getName());
            }
            for (Type st: supers) {
                if (supertype != null 
                        && st == supertype.getTypeModel()) {
                    callSuperclass(supertype, invoke, 
                            (Class)d, plist, that, false, superDecs, gen);
                } else {
                    TypeDeclaration typeDecl = st.getDeclaration();
                    final TypeDeclaration anoncont;
                    if (d.isAnonymous() 
                            && ModelUtil.contains(
                                    getContainingClassOrInterface(d.getContainer()), 
                                    typeDecl)) {
                        anoncont = getContainingClassOrInterface(d);
                    } else {
                        anoncont = null;
                    }
                    JsIdentifierNames names = gen.getNames();
                    if (anoncont == null) {
                        if (typeDecl.isStatic()) {
                            Declaration cont = getContainingDeclaration(typeDecl);
                            String qp = gen.qualifiedPath(that, cont, false);
                            gen.out(qp, 
                                    qp.isEmpty() ? "" : ".", 
                                    names.name(cont), 
                                    ".$st$.");
                        } else {
                            gen.qualify(that, typeDecl);
                        }
                        gen.out(names.name(typeDecl), "(");
                    } else {
                        gen.qualify(that, anoncont);
                        gen.out(names.name(typeDecl), ".call(",
                                names.self(getContainingClassOrInterface(
                                        d.getContainer())), 
                                ",");
                    }
                    if (typeDecl.isParameterized()) {
                        printTypeArguments(that, gen, d.isToplevel(), 
                                st.getTypeArguments(), null);
                        gen.out(",");
                    }
                    gen.out(names.self(d), ")");
                    gen.endLine(true);
                    copySuperMembers(typeDecl, superDecs, d, gen);
                }
            }
        } else if (supertype != null) {
            callSuperclass(supertype, invoke, (Class) d, plist, that, false, superDecs, gen);
        }
    }

    private static void copySuperMembers(final TypeDeclaration typeDecl, 
            final List<Declaration> decs, final ClassOrInterface d, 
            final GenerateJsVisitor gen) {
        if (!gen.opts.isOptimize() && decs != null) {
            for (Declaration dec: decs) {
                if (!typeDecl.isMember(dec)) { continue; }
                JsIdentifierNames names = gen.getNames();
                String suffix = names.scopeSuffix(dec.getContainer());
                if (dec instanceof Value 
                        && ((Value)dec).isTransient()) {
                    superGetterRef(dec,d,suffix, gen);
                    if (((Value) dec).isVariable()) {
                        superSetterRef(dec,d,suffix, gen);
                    }
                }
                else {
                    gen.out(names.self(d), ".", names.name(dec), suffix, "=",
                            names.self(d), ".", names.name(dec));
                    gen.endLine(true);
                }
            }
        }
    }

    private static void superGetterRef(final Declaration d, final ClassOrInterface sub,
            final String parentSuffix, final GenerateJsVisitor gen) {
        JsIdentifierNames names = gen.getNames();
        if (AttributeGenerator.defineAsProperty(d)) {
            gen.out(gen.getClAlias(), "copySuperAttr(", names.self(sub), ",'",
                    names.name(d), "','", parentSuffix, "')");
        }
        else {
            gen.out(names.self(sub), ".", names.getter(d, false), parentSuffix, "=",
                    names.self(sub), ".", names.getter(d, false));
        }
        gen.endLine(true);
    }

    private static void superSetterRef(final Declaration d, final ClassOrInterface sub,
            final String parentSuffix, final GenerateJsVisitor gen) {
        if (!AttributeGenerator.defineAsProperty(d)) {
            JsIdentifierNames names = gen.getNames();
            gen.out(names.self(sub), ".", names.setter(d), parentSuffix, "=",
                    names.self(sub), ".", names.setter(d));
            gen.endLine(true);
        }
    }

    public static class StaticTypeComparator 
            implements Comparator<Tree.StaticType> {
        @Override
        public int compare(StaticType o1, StaticType o2) {
            final Type t1 = o1.getTypeModel();
            final Type t2 = o2.getTypeModel();
            if (isTypeUnknown(t1)) {
                return isTypeUnknown(t2) ? 0 : -1;
            }
            if (isTypeUnknown(t2)) {
                return isTypeUnknown(t1) ? 0 : -1;
            }
            if (t1.isSubtypeOf(t2)) {
                return 1;
            }
            if (t2.isSubtypeOf(t1)) {
                return -1;
            }
            //Check the members
            for (Declaration d : t1.getDeclaration().getMembers()) {
                if (d instanceof TypedDeclaration 
                        || d instanceof ClassOrInterface) {
                    Declaration d2 = t2.getDeclaration()
                            .getMember(d.getName(), null, false);
                    if (d2 != null) {
                        final Declaration dd2 = 
                                getContainingDeclaration(d2);
                        if (dd2 instanceof TypeDeclaration 
                                && t1.getDeclaration()
                                    .inherits((TypeDeclaration)dd2)) {
                            return 1;
                        }
                    }
                }
            }
            for (Declaration d : t2.getDeclaration().getMembers()) {
                if (d instanceof TypedDeclaration 
                        || d instanceof ClassOrInterface) {
                    Declaration d2 = t1.getDeclaration()
                            .getMember(d.getName(), null, false);
                    if (d2 != null) {
                        final Declaration dd2 = 
                                getContainingDeclaration(d2);
                        if (dd2 instanceof TypeDeclaration 
                                && t2.getDeclaration()
                                    .inherits((TypeDeclaration)dd2)) {
                            return -1;
                        }
                    }
                }
            }
            return 0;
        }
    }

}
